import java.util.ArrayList;
import java.util.Random;

public class Bank {
	public long publicKey;
	private long privateKey;
	public long n;

	private int k = 10;
	private String aliceID = "Alice";
	private Random rand;
	private ArrayList<Integer> controlIndexes;
	private ArrayList<Long> BList;

	public Bank() {
		genKeys();
		controlIndexes = new ArrayList<Integer>();
		BList = new ArrayList<Long>();
		rand = new Random();
	}

	public ArrayList<Integer> withdrawalCoin(ArrayList<Long> BList) {
		this.BList = BList;
		controlIndexes.clear();
		// Sign the BList and return
		ArrayList<Integer> possibleIndexes = new ArrayList<Integer>();
		for (int i = 0; i < k * 2; i++) {
			possibleIndexes.add(i);
		}
		for (int i = 0; i < k; i++) {
			int tmp = rand.nextInt(possibleIndexes.size());
			controlIndexes.add(tmp);
			possibleIndexes.remove(tmp);
		}
		return controlIndexes;
	}

	public Long verifyAndSign(ArrayList<Long> a, ArrayList<Long> c,
			ArrayList<Long> d, ArrayList<Long> r) {
		//verifies calculations
		boolean verified = true;
		for (Integer i : controlIndexes) {
			long x = Customer.h(a.get(i) + c.get(i)); // h(a,c)
			// yi = h(ai  ID; di):
			long y = Customer.h(a.get(i) + h(aliceID) + Customer.h(d.get(i)));
			long b = Customer.pow(r.get(i), 3) * Customer.h(x + y);
			if (b != BList.get(i)) {
				verified = false;
				return null;
			}
		}
		//verified -> signing
		long signedCoin = 1;
		for (int i = 0; i < 2 * k; i++) {
			//if i is not part of controlIndexes
			// meaning that it should be calculated
			if (!controlIndexes.contains(i)) { 
				long signed = (long) (Math.pow(BList.get(i), privateKey) % n);
				signedCoin = signedCoin * signed;
			}
		}
		return signedCoin;
	}

	public long blindSignature(long B) {
		long signedB = Customer.pow(B, privateKey) % n;
		
		System.out.println();
		System.out.println("B:		" + B);
		System.out.println("Signed B:	" + signedB);
		System.out.println("Private key:	" + privateKey);
		
		System.out.println("B?:"+(privateKey*publicKey)%n);
		return signedB;
	}

	public static int h(String x) {
		char ch[];
		int M = 2500;
		ch = x.toCharArray();
		int xlength = x.length();

		int i, sum;
		for (sum = 0, i = 0; i < x.length(); i++)
			sum += ch[i];
		return sum % M;
	}

	/**
	 * Key generation -
	 * http://en.wikipedia.org/wiki/RSA_%28algorithm%29#Key_generation
	 */
	private void genKeys() {
		// 1. Choose two distinct prime numbers p and q.
		long p = getPrime(50); // 5
		long q = getPrime(p); // 11
		// 2. Compute n = pq.
		long n = p * q;
		// 3. Compute φ(n) = (p – 1)(q – 1), where φ is phi's
		// totient function.
		long phi = (q - 1) * (p - 1);
		// 4. Choose an integer e such that 1 < e < φ(n) and greatest common
		// divisor of (e, φ(n)) = 1; i.e., e and φ(n) are coprime.
		long e = getE(phi);
		// 5. Determine d as: d given (de) = 1 mod φ(n)
		long[] xY = extendedGcd(e, phi);
		long d = xY[0];
		// assign to private attributes
		System.out.println("d0:	" + d);
		while(d <= 0){
			d = d + phi;
		}
		privateKey = d;
		publicKey = e;
		this.n = n;
		
		System.out.println("d: 		" + d);
		System.out.println("e: 		" + e);
		System.out.println("n: 		" + n);
		System.out.println("phi: 	" + phi);
		System.out.println("d * e:	" + d * e);
		System.out.println("de mod phi = 1 ?: " + ((d * e) % phi == 1));

	}

	private long getE(long phi) {
		long i = 2;
		while (true) {
			if (gcd(i, phi) == 1) {
				return i;
			}
			i++;
		}
	}

	/**
	 * a <= b is a requirement
	 * @param a
	 * @param b
	 * @return
	 */
	private long gcd(long a, long b) {
		long rest = a % b;
		if (rest == 0) {
			return b;
		} else {
			return gcd(b, rest);
		}
	}

	/**
	 * Given an input a with b used as modulus, the 
	 * inverse of a is returned.
	 * @param a
	 * @param b
	 * @return
	 */
	public static long[] extendedGcd(long a, long b) {
		if (b == 0) {
			return new long[] { 1, 0 };
		} else {
			long q = a / b;
			long r = a % b;
			long[] s_t = extendedGcd(b, r);
			long s = s_t[0];
			long t = s_t[1];
			return new long[] { t, s - q * t };
		}
	}

	private long getPrime(long greaterThan) {
		while (true) {
			greaterThan++;
			if (isPrime(greaterThan)) {
				return greaterThan;
			}
		}
	}

	private boolean isPrime(long n) {
		if (n % 2 == 0)
			return false;
		for (int i = 3; i * i <= n; i += 2) {
			if (n % i == 0)
				return false;
		}
		return true;
	}
}
